/*
 * Copyright (c) 2013 Samsung Electronics Co., Ltd.
 * All rights reserved.
 *
 * This software is a confidential and proprietary information of Samsung
 * Electronics, Inc. ("Confidential Information").  You shall not disclose such
 * Confidential Information and shall use it only in accordance with the terms
 * of the license agreement you entered into with Samsung Electronics.
 */

package br.com.fpf.pontoapp.model.dao.impl;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;

import android.content.ContentValues;
import android.database.Cursor;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
import br.com.fpf.pontoapp.model.dao.IAbstractDAO;
import br.com.fpf.pontoapp.model.db.IDBManager;
import br.com.fpf.pontoapp.model.entity.AbstractBaseEntity;

import com.google.inject.Inject;

/**
 * The Class AbstractDAO.
 * 
 * @param <T>
 *            the generic type
 * @author ricardo.gastone
 */
public abstract class AbstractDAO<T extends AbstractBaseEntity> implements
		IAbstractDAO<T>, Serializable {

	/** The Constant serialVersionUID. */
	private static final long serialVersionUID = 1L;

	/** The Constant SQL_INSERT_OP. */
	protected static final String SQL_INSERT_OP = "insert";

	/** The Constant SQL_UPDATE_OP. */
	protected static final String SQL_UPDATE_OP = "update";

	/** The Constant ID_ENTITY. */
	protected static final long ID_ENTITY = -1;

	/** The Constant ROWS_AFFECTED. */
	protected static final int ROWS_AFFECTED = 0;

	/** Table Name for DAO *. */
	private final String table;

	/** The db manager. */
	@Inject
	private IDBManager dbManager;

	/**
	 * Instantiates a new abstract dao.
	 * 
	 * @param table
	 *            the table
	 */
	public AbstractDAO(final String table) {
		this.table = table;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * br.org.sidi.schef.base.model.dao.IAbstractDAO#insertAndCommit(java.lang
	 * .Object)
	 */
	@Override
	public long insertAndCommit(final T entity) throws SQLException {
		long entityId;

		// Begin a new database transaction
		beginTransaction();
		try {
			// Executing insert operation
			entityId = insert(entity);
			// Commit the transaction
			setTransactionSuccessful();
		} finally {
			// Finish the transaction, if an Exception was thrown a "rollback"
			// is executed !!!
			endTransaction();
		}

		return entityId;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * br.org.sidi.schef.base.model.dao.IAbstractDAO#insert(java.lang.Object)
	 */
	@Override
	public long insert(final T entity) throws SQLException {
		// Convert Object to be used in SQL Persist Command
		final ContentValues values = objectToContentValues(entity);
		// Executing insert operation
		return getDb().insert(table, null, values);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * br.org.sidi.schef.base.model.dao.IAbstractDAO#insertAllAndCommit(java
	 * .util.List)
	 */
	@Override
	public long[] insertAllAndCommit(final List<T> entityList)
			throws SQLException {
		long[] ids;
		// Begin a new database transaction
		beginTransaction();
		try {
			// Executing insert operation
			ids = insertAll(entityList);
			// Commit the transaction
			setTransactionSuccessful();
		} finally {
			// Finish the transaction, if an Exception was thrown a "rollback"
			// is executed !!!
			endTransaction();
		}

		return ids;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * br.org.sidi.schef.base.model.dao.IAbstractDAO#insertAll(java.util.List)
	 */
	@Override
	public long[] insertAll(final List<T> entityList) throws SQLException {
		final long[] ids = new long[entityList.size()];

		for (int i = 0; i < entityList.size(); i++) {
			// Convert Object to be used in SQL Persist Command
			final ContentValues values = objectToContentValues(entityList
					.get(i));
			// Executing insert operation
			ids[i] = getDb().insert(table, null, values);
		}

		return ids;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * br.org.sidi.schef.base.model.dao.IAbstractDAO#updateAndCommit(java.lang
	 * .Object)
	 */
	@Override
	public int updateAndCommit(final T entity) throws SQLException {
		int rowsAffected = ROWS_AFFECTED;

		// Begin a new database transaction
		beginTransaction();
		try {
			// Execute an update operation
			rowsAffected += update(entity);
			// Commit the transaction
			setTransactionSuccessful();
		} finally {
			// Finish the transaction, if an Exception was thrown a "rollback"
			// is executed !!!
			endTransaction();
		}

		return rowsAffected;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * br.org.sidi.schef.base.model.dao.IAbstractDAO#update(java.lang.Object)
	 */
	@Override
	public int update(final T entity) throws SQLException {
		int rowsAffected = ROWS_AFFECTED;

		// Convert Object to be used in SQL Persist Command
		final ContentValues values = objectToContentValues(entity);

		// Get the conditions for executing an update
		final String whereClause = getPrimaryKeyWhereClause();
		final String[] whereArgs = getPrimaryKeyFromObject(entity);

		// Execute an update operation
		rowsAffected += getDb().update(table, values, whereClause, whereArgs);

		return rowsAffected;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * br.org.sidi.schef.base.model.dao.IAbstractDAO#updateAllAndCommit(java
	 * .util.List)
	 */
	@Override
	public int updateAllAndCommit(final List<T> entityList) throws SQLException {
		int rowsAffecteds = ROWS_AFFECTED;

		// Begin a new database transaction
		beginTransaction();
		try {
			// Execute an update operation
			rowsAffecteds += updateAll(entityList);
			// Commit the transaction
			setTransactionSuccessful();
		} finally {
			// Finish the transaction, if an Exception was thrown a "rollback"
			// is executed !!!
			endTransaction();
		}

		return rowsAffecteds;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * br.org.sidi.schef.base.model.dao.IAbstractDAO#updateAll(java.util.List)
	 */
	@Override
	public int updateAll(final List<T> entityList) throws SQLException {
		int rowsAffecteds = ROWS_AFFECTED;

		for (int i = 0; i < entityList.size(); i++) {
			// Convert Object to be used in SQL Persist Command
			final ContentValues values = objectToContentValues(entityList
					.get(i));

			// Get the conditions for executing an update
			final String whereClause = getPrimaryKeyWhereClause();
			final String[] whereArgs = getPrimaryKeyFromObject(entityList
					.get(i));

			// Execute an update operation
			rowsAffecteds += getDb().update(table, values, whereClause,
					whereArgs);
		}

		return rowsAffecteds;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * br.org.sidi.schef.base.model.dao.IAbstractDAO#deleteAndCommit(java.lang
	 * .Object)
	 */
	@Override
	public int deleteAndCommit(final T entity) throws SQLException {
		int rowsAffecteds = ROWS_AFFECTED;

		// Begin a new database transaction
		beginTransaction();
		try {
			// Execute an delete operation
			rowsAffecteds += delete(entity);

			// Commit the transaction
			setTransactionSuccessful();
		} finally {
			// Finish the transaction, if an Exception was thrown a "rollback"
			// is executed !!!
			endTransaction();
		}

		return rowsAffecteds;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * br.org.sidi.schef.base.model.dao.IAbstractDAO#delete(java.lang.Object)
	 */
	@Override
	public int delete(final T entity) throws SQLException {
		int rowsAffecteds = ROWS_AFFECTED;

		// Get the conditions for executing an update
		final String whereClause = getPrimaryKeyWhereClause();
		final String[] whereArgs = getPrimaryKeyFromObject(entity);

		// Execute an delete operation
		rowsAffecteds += getDb().delete(table, whereClause, whereArgs);

		return rowsAffecteds;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * br.org.sidi.schef.base.model.dao.IAbstractDAO#deleteAllAndCommit(java
	 * .util.List)
	 */
	@Override
	public int deleteAllAndCommit(final List<T> entityList) throws SQLException {
		int rowsAffecteds = ROWS_AFFECTED;

		// Begin a new database transaction
		beginTransaction();
		try {
			// Execute an delete operation
			rowsAffecteds += deleteAll(entityList);

			// Commit the transaction
			setTransactionSuccessful();
		} finally {
			// Finish the transaction, if an Exception was thrown a "rollback"
			// is executed !!!
			endTransaction();
		}

		return rowsAffecteds;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * br.org.sidi.schef.base.model.dao.IAbstractDAO#deleteAll(java.util.List)
	 */
	@Override
	public int deleteAll(final List<T> entityList) throws SQLException {
		int rowsAffecteds = ROWS_AFFECTED;

		for (int i = 0; i < entityList.size(); i++) {
			// Get the conditions for executing an update
			final String whereClause = getPrimaryKeyWhereClause();
			final String[] whereArgs = getPrimaryKeyFromObject(entityList
					.get(i));

			// Execute an delete operation
			rowsAffecteds += getDb().delete(table, whereClause, whereArgs);
		}

		return rowsAffecteds;
	}

	/**
	 * Helper method to parse Cursor do object List.
	 * 
	 * @param cursor
	 *            records queried in table
	 * @return List of Model objects
	 */
	protected List<T> parseCursorToList(final Cursor cursor) {
		final List<T> result = new ArrayList<T>();

		// If records are found then add in result list
		if (cursor != null) {
			if (cursor.moveToFirst()) {
				do {
					result.add(cursorToObject(cursor));
				} while (cursor.moveToNext());
			}
			cursor.close();
		}

		return result;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see br.org.sidi.schef.base.model.dao.IAbstractDAO#getAll()
	 */
	@Override
	public List<T> getAll() {
		// Query database table for all records
		final Cursor cursor = getDb().query(table, null, null, null, null,
				null, null);

		return parseCursorToList(cursor);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see br.org.sidi.schef.base.model.dao.IAbstractDAO#getEntityById(long)
	 */
	@Override
	public T getEntityById(final long entityId) {
		// Query database table for all records

		T result = null;

		final Cursor cursor = getDb().query(table, null,
				getPrimaryKeyWhereClause(), getPrimaryKeyFromObject(entityId),
				null, null, null);

		final List<T> resultList = parseCursorToList(cursor);
		if (!resultList.isEmpty()) {
			result = resultList.get(0);
		}

		return result;
	}

	/**
	 * Get entity list creating the consult by a map with field and value.
	 * 
	 * @param filters
	 *            the filters
	 * @param orderBy
	 *            the order by
	 * @return the list
	 */
	public List<T> getList(final Map<String, String> filters,
			final String orderBy) {
		return getList(getTable(), filters, orderBy);
	}

	/**
	 * Gets the list sql.
	 * 
	 * @param sql
	 *            the sql
	 * @param filters
	 *            the filters
	 * @param orderBy
	 *            the order by
	 * @return the list sql
	 */
	protected List<T> getListSQL(final String sql,
			final Map<String, String> filters, final String orderBy) {
		final String selection = builderSelection(filters);
		final StringBuilder query = new StringBuilder(sql);
		if (!selection.isEmpty()) {
			query.append(" Where ").append(selection);
		}
		if (orderBy != null && !orderBy.isEmpty()) {
			query.append(" order by ").append(orderBy);
		}
		final String[] selectionArgs = builderSelectionArgs(filters);
		final Cursor cursor = getDb().rawQuery(query.toString(), selectionArgs);
		return parseCursorToList(cursor);
	}

	/**
	 * Get entity list creating the consult by a map with field and value.
	 * 
	 * @param table
	 *            the table
	 * @param filters
	 *            the filters
	 * @param orderBy
	 *            the order by
	 * @return the list
	 */
	protected List<T> getList(final String table,
			final Map<String, String> filters, final String orderBy) {
		final String selection = builderSelection(filters);
		final String[] selectionArgs = builderSelectionArgs(filters);
		final Cursor cursor = getDb().query(table, null, selection,
				selectionArgs, null, null, orderBy);
		return parseCursorToList(cursor);
	}

	/**
	 * Create the string filter.
	 * 
	 * @param filter
	 *            the filter
	 * @return the string
	 */
	protected String builderSelection(final Map<String, String> filter) {
		final StringBuilder builder = new StringBuilder();
		for (final String field : filter.keySet()) {
			builder.append(field);
			builder.append(" = ?");
			// builder.append(field);
			builder.append(" and ");
		}
		if (!builder.toString().isEmpty()) {
			builder.delete(builder.length() - 5, builder.length());
		}
		return builder.toString();
	}

	/**
	 * Binding the values with parameters.
	 * 
	 * @param filter
	 *            the filter
	 * @return the string[]
	 */
	protected String[] builderSelectionArgs(final Map<String, String> filter) {
		final Object[] objectArray = filter.values().toArray();
		final String[] stringArray = Arrays.copyOf(objectArray,
				objectArray.length, String[].class);
		return stringArray;
	}

	/**
	 * Get the Primary Key where clause to model object.
	 * 
	 * @return The Primary Key where clause
	 */
	protected String getPrimaryKeyWhereClause() {
		return AbstractBaseEntity.ID_COLUMN + " = ?";
	}

	/**
	 * Begin a transaction.
	 */
	private void beginTransaction() {
		getDbManager().beginTransaction();
	}

	/**
	 * Set a transaction with successful.
	 */
	private void setTransactionSuccessful() {
		getDbManager().setSuccessfull();
	}

	/**
	 * Finalize a transaction.
	 */
	private void endTransaction() {
		getDbManager().endTransaction();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see br.org.sidi.schef.base.model.dao.IAbstractDAO#getDb()
	 */
	@Override
	public SQLiteDatabase getDb() {
		return getDbManager().getDatabase();
	}

	/**
	 * Get the database manipulator.
	 * 
	 * @return instance of the DBManager
	 */
	private IDBManager getDbManager() {
		return dbManager;
	}

	/**
	 * Get table name.
	 * 
	 * @return string with the name of table
	 */
	public String getTable() {
		return table;
	}

	/**
	 * Get the array of Primary Key values.
	 * 
	 * @param entity
	 *            Model Object
	 * @return The array with the Primary Key
	 */
	protected String[] getPrimaryKeyFromObject(final T entity) {
		return getPrimaryKeyFromObject(entity.getId());
	}

	/**
	 * Convert the primary key of an entity in a String[].
	 * 
	 * @param entityId
	 *            id of the entity
	 * @return String[] with primary key value
	 */
	protected String[] getPrimaryKeyFromObject(final long entityId) {
		return new String[] { String.valueOf(entityId) };
	}

	/**
	 * Convert a Cursor record in Model Object.
	 * 
	 * @param cursor
	 *            Cursor with a record
	 * @return A Model Object with the Cursor data
	 */
	protected abstract T cursorToObject(final Cursor cursor);

	/**
	 * Convert a Model Object in ContentValues.
	 * 
	 * @param entity
	 *            Model Object
	 * @return A ContentValues with the Model Object data
	 */
	protected abstract ContentValues objectToContentValues(final T entity);

}
